from __future__ import print_function
# from myAI.asynchronous.RAE import RAE1,EndCriticalRegion
from myAI.asynchronous.paralize_RAE import RAE1, IpcArgs, EndCriticalRegion

# from myAI.asynchronous.RAE import IpcArgs
import myAI.asynchronous.domain
import threading
import inspect
import ctypes
import time
import copy
__author__ = 'chengkai'

problem_module = None

class Agenda():
    def __init__(self):
        self.lastActiveStack = 0        # keeps track of the last stack that was Progressed
        self.numstacks = 0              # keeps track of the total number of stacks
        # 保存上层AI分配任务的镜像，每个算子当前只有一个任务,例如{14983: ['maneuver', 2517]}
        self.rae_task = None
        # 用于打印输出，不是很重要
        self.taskInfo = {}
        # 保存不同任务堆栈线程的共享变量，以便于agenda管理
        self.ipcArgs = IpcArgs()
        # 保存兵棋态势镜像
        self.rae_observation = None
        # 保存AI态势镜像
        self.rae_situation = None
        self.last_step = -1

    def agenda_reset(self):
        # self.terminate_Allthread()
        self.rae_task = None
        self.rae_observation = None
        self.rae_situation = None
        self.last_step = -1
        self.ipcArgs.ipcargs_reset()
        self.ipcArgs = None




    def CreateNewStack(self,taskInfo, raeArgs, myAgent):
        stackid = raeArgs.stack
        retcode = RAE1(raeArgs.task, raeArgs, myAgent)
        # retcode, retryCount, height, taskCount, commandCount = RAE1(raeArgs.task, raeArgs,myAgent)
        # taskInfo[stackid] = ([raeArgs.task] + raeArgs.taskArgs, retcode, retryCount, height, taskCount, commandCount)


        # 如果有command失败，最终回溯到最顶层的任务，即上层AI分配给该算子的任务,返回值为Failure，线程随即结束
        # 需要告知上层AI，本算子任务是正常结束还是失败

        if retcode == 'Success':
            if self.ipcArgs.threadDict.get(stackid):
                print(f'Thread success: obj_id = {stackid}, task = {self.ipcArgs.threadDict.get(stackid)[-1]}')
                self.ipcArgs.threadDict[stackid] = ['Success']

        elif retcode == 'Failure':
            if self.ipcArgs.threadDict.get(stackid):
                print(f'Thread failure: obj_id = {stackid}, task = {self.ipcArgs.threadDict.get(stackid)[-1]}')
                self.ipcArgs.threadDict[stackid] = ['Failure']


        # 如果线程执行异常错误，将线程相关信息清除
        elif retcode == 'Error':
            if self.ipcArgs.threadDict.get(stackid):
                print(f'Thread error: obj_id = {stackid}, task = {self.ipcArgs.threadDict.get(stackid)[-1]}')
                self.ipcArgs.threadDict.pop(stackid)

        else:
            pass


    def PrintResult(self,taskInfo):
        print('ID ', '\t', 'Task',
              '\t\t\t', 'Result',
              '\t\t\t', 'Retry Count',
              '\t\t\t', 'Efficiency',
              '\t\t\t', 'h',
              '\t\t\t', 't',
              '\t\t\t', 'c',
              '\n')
        for stackid in taskInfo:
            args, res, retryCount, eff, height, taskCount, commandCount = taskInfo[stackid]

            print(stackid, '\t', 'Task {}{}'.format(args[0], args[1:]),
                  '\t\t\t', res,
                  '\t\t\t', retryCount,
                  '\t\t\t', eff,
                  '\t\t\t', height,
                  '\t\t\t', taskCount,
                  '\t\t\t', commandCount,
                  '\n')


    def PrintResultSummary(self,taskInfo):
        succ = 0
        fail = 0
        retries = 0
        effTotal = 0
        h = 0
        t = 0
        c = 0
        for stackid in taskInfo:
            args, res, retryCount, eff, height, taskCount, commandCount = taskInfo[stackid]
            if res == 'Success':
                succ += 1
            else:
                fail += 1
            retries += retryCount
            effTotal += eff.GetValue()
            c += commandCount
            t += taskCount
            if height > h:
                h = height
        print(succ, succ + fail, retries, h, t, c)
        # print(' '.join('-'.join([key, str(cmdNet[key])]) for key in cmdNet))

    def checkBeginNew(self, obj_id, task, myAgent):
        # if task is not None and len(task) > 1:
        if task is not None:
            if obj_id not in self.ipcArgs.threadDict.keys():
                return True
        return False

    def checkWaitingUp(self, obj_id, task, myAgent):
        if task is not None:
            if obj_id not in self.ipcArgs.threadDict.keys():
                return True
        return False

    def checkTaskComplete(self, obj_id, task, myAgent):
        if task is not None:
            # if self.ipcArgs.threadDict[obj_id][0] == 'Success' or self.ipcArgs.threadDict[obj_id][0] == 'Failure':
            if self.ipcArgs.threadDict.get(obj_id)[0] in ['Failure', 'Success']:
                return True
        return False

    def checkStopAndBeginNew(self, obj_id, task, myAgent):
        if task is not None:
            if obj_id in self.ipcArgs.threadDict.keys():
                if obj_id in self.get_ourids_observation(myAgent):
                    if self.get_bop(obj_id)['lose_control'] == 0:
                        if task != self.ipcArgs.threadDict.get(obj_id)[1]:
                            return True
        return False

    def checkContinueExist(self, obj_id, task, myAgent):
        if task is not None:
            if obj_id in self.ipcArgs.threadDict.keys():
                if obj_id in self.get_ourids_observation(myAgent):
                    if self.get_bop(obj_id)['lose_control'] == 0:
                        if task == self.ipcArgs.threadDict.get(obj_id)[1]:
                            return True
        return False

    def checkTerminateLoseControl(self, obj_id, task, myAgent):
        if task is not None:
            if obj_id in self.ipcArgs.threadDict.keys():
                if obj_id in self.get_ourids_observation(myAgent):
                    if self.get_bop(obj_id)['lose_control'] == 1:
                        return True
        return False

    def checkTerminateUpDead(self, obj_id, task, myAgent):
        if myAgent.agenda.rae_observation is None:
            return False
        if task is None:
            if self.ipcArgs.threadDict.get(obj_id):
                return True
        return False

    def checkTerminateDownDead(self, obj_id, task, myAgent):
        if task is not None:
            if obj_id in self.ipcArgs.threadDict.keys():
                # if self.ipcArgs.threadDict[obj_id][0] not in ['Failure', 'Success']:
                if obj_id not in self.get_ourids_observation(myAgent):
                    return True
        return False

    def get_bop(self, obj_id):
        """
        Get bop in rae observation based on its id.
        """
        for bop in self.rae_observation['operators']:
            if obj_id == bop['obj_id']:
                return bop
        for bop in self.rae_observation['passengers']:
            if obj_id == bop['obj_id']:
                return bop
        return None

    def get_enemy_info(self, obj_id):
        for enemy_info in self.rae_situation.enemies:
            if enemy_info['obj_id'] == obj_id:
                return enemy_info
        return None

    def get_enemy(self, obj_id):
        for enemy_info in self.rae_situation.enemies:
            if enemy_info['obj_id'] == obj_id:
                return enemy_info['operator']
        return None

    def get_city(self, city_loc):
        """ 2020-4-22
        通过夺控点位置，找出夺控点对象（本函数应该放在myAgent类的get_bop函数一起，建议后期移植）
        """
        for city in self.rae_observation['cities']:
            if city['coord'] == city_loc:
                return city
        return None

    # 从observation获取指定我方的算子id列表
    def get_ourids_observation(self, myAgent):
        """
        Get ourids in rae observation.
        """
        observation_ids = [opr['obj_id'] for opr in self.rae_observation['operators'] if opr['color'] == myAgent.color]
        passenger_ids = [opr['obj_id'] for opr in self.rae_observation['passengers'] if opr['color'] == myAgent.color]
        our_ids = observation_ids + passenger_ids
        return our_ids

    #从初始想定中得到我方算子id列表
    def getOurIDs(self, myAgent):
        OurIDs = []
        if myAgent.color == 0:
            for opr in myAgent.scenario_info[0]['operators']:
                OurIDs.append(opr['obj_id'])
            for pas in myAgent.scenario_info[0]['passengers']:
                OurIDs.append(pas['obj_id'])
        else:
            for opr in myAgent.scenario_info[1]['operators']:
                OurIDs.append(opr['obj_id'])
            for pas in myAgent.scenario_info[1]['passengers']:
                OurIDs.append(pas['obj_id'])
        return OurIDs

    def raeMult(self, myAgent):
        # while True:
        while myAgent.thread_rM is not None:
            faction_task_ids = []
            if myAgent.observation is None:
                break
            if myAgent.observation['time']['cur_step'] <= self.last_step:
                time.sleep(0.05)
                continue

            # self.ipcArgs.mutex_observation.acquire()
            # self.rae_observation = copy.deepcopy(myAgent.observation)
            self.rae_observation = myAgent.observation
            # self.ipcArgs.mutex_observation.release()

            # if self.rae_observation['time']['cur_step'] <= self.last_step:
            #     continue

            self.ipcArgs.mutex_situation.acquire()
            self.rae_situation = copy.deepcopy(myAgent.situations)
            self.ipcArgs.mutex_situation.release()
            # print(f'\t|- rae_situation enemies: {len(self.rae_situation.enemies)}')

            self.ipcArgs.mutex_task.acquire()
            self.rae_task = copy.deepcopy(myAgent.factionAI.task)
            self.ipcArgs.mutex_task.release()

            # 用于查看执行引擎子线程中是否存在跳帧的现象
            # print(f'        rae current step: {self.rae_observation["time"]["cur_step"]}')

            # 一个step进来，每个任务算子输出一个动作后停止
            for obj_id in self.rae_task.keys():
                # 检查是否终止上层AI判断算子死亡，tasks中没有任务，threadDict中有任务，算子从tasks中判断已经死亡
                if self.checkTerminateUpDead(obj_id, self.rae_task.get(obj_id), myAgent):
                    faction_task_ids.append(obj_id)
                    print(f'【1】obj_id = {obj_id} is Dead!!!  StopTask = {self.rae_task.get(obj_id)}')
                    if len(self.ipcArgs.threadDict.get(obj_id)) > 1:
                        self.terminate_thread(self.ipcArgs.threadDict.get(obj_id)[0])
                    self.ipcArgs.threadDict.pop(obj_id)
                    self.ipcArgs.stack_signal.pop(obj_id)

                # 检查是否终止下层AI判断算子的死亡，tasks和threadDict中均有任务，但算子从态势中判断已经死亡
                elif self.checkTerminateDownDead(obj_id, self.rae_task.get(obj_id), myAgent):
                    faction_task_ids.append(obj_id)
                    print(f'【2】obj_id = {obj_id} is Dead!!!  StopTask = {self.rae_task.get(obj_id)}')
                    if len(self.ipcArgs.threadDict.get(obj_id)) > 1:
                        self.terminate_thread(self.ipcArgs.threadDict.get(obj_id)[0])
                    self.ipcArgs.threadDict.pop(obj_id)
                    self.ipcArgs.stack_signal.pop(obj_id)

                # 检查是否中断失去控制算子的任务，tasks和threadDict中均有任务，但算子已失去控制
                elif self.checkTerminateLoseControl(obj_id, self.rae_task.get(obj_id), myAgent):
                    faction_task_ids.append(obj_id)
                    print(f'【3】obj_id = {obj_id} is lose control!!!  StopTask = {self.rae_task.get(obj_id)}')
                    if len(self.ipcArgs.threadDict.get(obj_id)) > 1:
                        self.terminate_thread(self.ipcArgs.threadDict.get(obj_id)[0])
                    self.ipcArgs.threadDict.pop(obj_id)
                    self.ipcArgs.stack_signal.pop(obj_id)

                # 检查是否创建一个新的任务线程，tasks有任务记录，threadDict没有任务记录
                elif self.checkBeginNew(obj_id, self.rae_task.get(obj_id), myAgent):
                    self.BeginCreateStack(obj_id, self.rae_task.get(obj_id), myAgent)
                    print(f'Begin new: obj_id = {obj_id},task = {self.rae_task.get(obj_id)}')

                # 检查是否等待上层AI赋予新的任务，tasks中的任务记录是已经执行过的（成功或失败），threadDict没有任务记录（被删除掉了）
                # elif self.checkWaitingUp(obj_id, self.rae_task.get(obj_id), myAgent):
                #     # self.ipcArgs.sem.release()
                #     pass
                # 检查任务完成情况，tasks中的任务记录是已经执行过的（成功或失败），threadDict中的任务记录是已经执行过的（成功或失败）
                elif self.checkTaskComplete(obj_id, self.rae_task.get(obj_id), myAgent):
                    faction_task_ids.append(obj_id)
                    # self.task_copy.pop(obj_id)
                    self.ipcArgs.threadDict.pop(obj_id)
                    self.ipcArgs.stack_signal.pop(obj_id)
                    print(f'RAE complete: obj_id = {obj_id},task = {self.rae_task.get(obj_id)}')

                # 检查是否继续执行之前的任务，tasks和threadDict中均有正在被执行的任务且二者一致
                elif self.checkContinueExist(obj_id, self.rae_task.get(obj_id), myAgent):
                    # 主线程将激活权限给子线程
                    # print(f'【4】obj_id = {obj_id} continue to task = {self.ipcArgs.threadDict[obj_id][1]}')
                    self.ipcArgs.stack_signal[obj_id] = 1

                # 检查是否中断之前的任务，创建新的任务线程，tasks和threadDict中均有任务，且二者不一致
                elif self.checkStopAndBeginNew(obj_id, self.rae_task.get(obj_id), myAgent):
                    print(f'Stop last: obj_id = {obj_id},task = {self.ipcArgs.threadDict.get(obj_id)[1]}')
                    if len(self.ipcArgs.threadDict.get(obj_id)) > 1:
                        self.terminate_thread(self.ipcArgs.threadDict.get(obj_id)[0])
                    self.ipcArgs.threadDict.pop(obj_id)
                    self.ipcArgs.stack_signal.pop(obj_id)
                    self.BeginCreateStack(obj_id, self.rae_task.get(obj_id), myAgent)
                    print(f'Begin new again: obj_id = {obj_id},task = {self.rae_task.get(obj_id)}')

            # 对于每个算子执行完一步后，更新上层AI任务列表
            if len(faction_task_ids) > 0:
                self.ipcArgs.mutex_task.acquire()
                # myAgent.factionAI.task = copy.deepcopy(self.task_copy)
                # 将死亡、失控、完成、失败的任务删除
                for faction_task_id in faction_task_ids:
                    if faction_task_id in myAgent.factionAI.task.keys():
                        myAgent.factionAI.task.pop(faction_task_id)
                self.ipcArgs.mutex_task.release()

            # 保证所有算子在一个step都输出动作后，再进入下一个step
            # while 1 in self.ipcArgs.stack_signal.values():
            #     pass

            self.last_step = self.rae_observation['time']['cur_step']

            # 让任务线程自己退出
            # if (self.last_step >= myAgent.max_step-2) and (self.last_step <= myAgent.max_step):
            #     break
        print(f'game over: thread_rM exit')

    #创建任务堆栈线程，并激活该线程执行command
    def BeginCreateStack(self, obj_id, taskParams, myAgent):
        '''
        :param obj_id: 算子ID
        :param taskParams: 上层AI分配的任务，包括任务名称和参数
        :param myAgent: AI对象
        :return:
        '''
        if taskParams != []:
            # 创建局部对象，用于保存任务信息，作为参数传给CreateNewStack
            raeArgs = RaeArgs()
            # 用obj_id作为堆栈标识
            raeArgs.stack = obj_id
            # 任务名称
            raeArgs.task = taskParams[0]
            # 任务参数，包括obj_id
            raeArgs.taskArgs = [obj_id] + taskParams[1:]
            # 创建子线程，并将线程对象和任务保存在threadDict中
            self.ipcArgs.threadDict[obj_id] = [threading.Thread(target=self.CreateNewStack, args=(self.taskInfo, raeArgs, myAgent)), taskParams]
            # # 设置子线程为守护线程，在主线程结束后，任务堆栈子线程此时被回收
            # self.ipcArgs.threadDict[obj_id][0].setDaemon(True)
            self.ipcArgs.threadDict.get(obj_id)[0].start()
            # 激活本线程的权限
            self.ipcArgs.stack_signal[obj_id] = 1

    def terminate_thread(self, thread):
        if not thread.isAlive():
            return
        exc = ctypes.py_object(SystemExit)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread.ident), exc)
        if res == 0:
            raise ValueError("nonexistent thread id")
        elif res > 1:
            ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")

    def terminate_Allthread(self):
        # for thread_and_task in self.ipcArgs.threadDict.values():
        #     if len(thread_and_task) > 1:
        #         self.terminate_thread(thread_and_task[0])

        # for obj_id, thread_and_task in self.ipcArgs.threadDict.items():
        obj_ids = self.ipcArgs.threadDict.keys()
        for obj_id in obj_ids:
            if self.ipcArgs.threadDict.get(obj_id) is not None:
                # 排除已经完成或者失败的任务线程
                thread_and_task = self.ipcArgs.threadDict.get(obj_id)
                if len(thread_and_task) > 1:
                    self.terminate_thread(thread_and_task[0])
                    print(f'game over: our_ops {obj_id} thread exit')

# 创建全局对象
# agenda = Agenda()

# 记录本线程的基本信息
class RaeArgs():
    def __init__(self):
        pass